import path = require("path");
import fse = require("fs-extra");
// @ts-ignore
import npminstall = require("npminstall");
import { log } from "@x.render/render-node-utils";
import { USER_HOME_PATH } from "@x.render/render-node-utils/lib/constant";
import getNpmPkgLatestVersion = require("@x.render/render-node-utils/lib/getNpmPkgLatestVersion");
import pathExists = require("path-exists");

interface Options {
  version?: string;
  pkgName: string;
  storePath?: string;
  pkgPath?: string;
  localPkgCachePath?: string;
}

class Package {
  version?: string;
  pkgName: string;
  storePath?: string;
  pkgPath?: string;
  localPkgCachePath?: string;

  constructor(
    options: Options = {
      version: "latest",
      pkgName: "",
      storePath: "packages",
      pkgPath: null,
    },
  ) {
    this.version = options.version;
    this.pkgName = options.pkgName;
    this.storePath = options.storePath;
    this.pkgPath = options.pkgPath;
    this.localPkgCachePath = options.localPkgCachePath;
  }

  async prepare() {
    if (this.version === "latest" || !this.version) {
      this.version = await this.getNpmPkgLatestVersion();
    }
    const LOCAL_PKG_PATH = path.resolve(
      USER_HOME_PATH,
      this.localPkgCachePath || "render-package",
    );
    this.pkgPath = path.resolve(LOCAL_PKG_PATH, this.storePath);
    fse.ensureDir(this.pkgPath);
  }

  async getNpmPkgLatestVersion() {
    return await getNpmPkgLatestVersion(this.pkgName);
  }

  async install(version?: string) {
    await this.prepare();

    if (!(await this.canIInstall())) {
      log.debug(
        "the current version package already exists, no download operation be executed.",
      );
      return;
    }

    return npminstall({
      root: this.pkgPath,
      storeDir: path.resolve(this.pkgPath, this.pkgName),
      pkgs: [
        {
          name: this.pkgName,
          version: version || this.version,
        },
      ],
    });
  }

  async canIInstall() {
    const latestVersion = await this.getNpmPkgLatestVersion();
    return (
      !(await this.isLocalPkgExist(latestVersion)) &&
      !(await this.isLocalPkgExist(this.version))
    );
  }

  async isLocalPkgExist(version?: string) {
    await this.prepare();
    return pathExists.sync(this.localPkgPath(version));
  }

  async update() {
    await this.prepare();
    const latestVersion = await this.getNpmPkgLatestVersion();
    if (!(await this.isLocalPkgExist(latestVersion))) {
      await this.deleteOldVersionPkg();
      await this.install(latestVersion);
    }
  }

  async deleteOldVersionPkg() {
    fse.emptyDirSync(path.resolve(this.pkgPath, this.pkgName));
  }

  localPkgPath(version: string) {
    let { pkgName } = this;

    // handing package paths with '@' symbols
    if (pkgName.startsWith("@")) {
      pkgName = pkgName.replace(/\//g, "_");
    }
    return path.resolve(
      this.pkgPath,
      this.pkgName,
      `_${pkgName}@${version || this.version}@${this.pkgName}`,
    );
  }

  async getPkgCachePath() {
    const latestVersion = await this.getNpmPkgLatestVersion();

    if (await this.isLocalPkgExist(latestVersion)) {
      return this.localPkgPath(latestVersion);
    }

    if (await this.isLocalPkgExist(this.version)) {
      return this.localPkgPath(this.version);
    }
  }
}

export = Package;
